<?php

/*
 * Copyright (C) 2006-2009 Pham Cong Dinh
 *
 * This file is part of Pone.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * This class provides a basic service for managing a set of database access objects.
 *
 * This class can be used beyond Spica MVC context.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      October 18, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ConnectionFactory.php 1711 2010-02-23 18:04:12Z pcdinh $
 */
class SpicaConnectionFactory
{
    /**
     * Prevents instantiation
     */
    private function __construct() {}

    /**
     * Constructs an object of <code>SpicaConnection</code> that represents a database
     * connection to the given database server
     *
     * @throws SpicaDatabaseException if database vendor is not specified or specified database vendor is unknown
     * @param  array $config Database access configuration
     * @return SpicaConnection
     */
    public static function getConnection($config)
    {
        if (false === isset($config['dbvendor']))
        {
            throw new SpicaDatabaseException('Unable to establish a database connection: database vendor name is missing. Please check key "dbvendor" in the configuration array. That piece of information is very important to load suitable database adapter. Currently, this library supports: mysql. The upcoming are: oracle, sqlite3, postgresql, sqlserver, drizzle.');
        }

        switch ($config['dbvendor'])
        {
            case SpicaDatabaseVendor::ORACLE:
                include_once 'library/spica/core/datasource/db/oracle/Connection.php';
                return new SpicaOracleConnection($config);

            case SpicaDatabaseVendor::MYSQL: // MySQL 5.0.1 or higher is required
                include_once 'library/spica/core/datasource/db/mysql/Connection.php';
                return new SpicaMySQLConnection($config);

            case SpicaDatabaseVendor::POSTGRESQL:
                include_once 'library/spica/core/datasource/db/postgresql/Connection.php';
                return new SpicaPosgreSQLConnection($config);

            case SpicaDatabaseVendor::SQLITE3:
                include_once 'library/spica/core/datasource/db/sqlite/Connection.php';
                return new SpicaSQLite3Connection($config);

            case SpicaDatabaseVendor::SQLSERVER:
                include_once 'library/spica/core/datasource/db/sqlserver/Connection.php';
                return new SpicaSQLServerConnection($config);

            case SpicaDatabaseVendor::DRIZZLE:
                include_once 'library/spica/core/datasource/db/drizzle/Connection.php';
                return new SpicaDrizzleConnection($config);

            default:
                throw new SpicaDatabaseException('Unable to establish a database connection. Unknown database vendor '.$config['dbvendor'].'. Currently, this library supports: mysql. The upcoming are: sqlite3, postgresql, sqlserver, drizzle.');
        }
    }

    /**
     * Returns the current version of the library
     *
     * @var string
     */
    public static function version()
    {
        return '0.9_b267';
    }
}

/**
 * An exception that provides information on a database access error or other errors
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      October 18, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaDatabaseException extends Exception
{
    /**
     * Previous exception
     *
     * @var SpicaDatabaseException
     */
    protected $_previousException;

    /**
     * A string which follows the XOPEN SQLstate conventions or the SQL:2003 conventions
     *
     * @var string
     */
    protected $_sqlState;

    /**
     * Vendor specific native error message
     *
     * @var string
     */
    protected $_nativeMessage;

    /**
     * A database vendor-specific exception code
     *
     * @var string
     */
    protected $code;

    /**
     * Constructs an SpicaDatabaseException object; the $applicationMessage field defaults to null,
     * the $sqlState field defaults to null, the vendorCode field defaults to 0 and the previous exception object
     * defaults to null as well
     *
     * @param string $applicationMessage
     * @param string $nativeMessage
     * @param string $sqlState An XOPEN or SQL:2003 code identifying the exception
     * @param int    $vendorCode A database vendor-specific exception code
     * @param Exception $exception
     */
    public function __construct($applicationMessage = null, $nativeMessage = null, $sqlState = null, $vendorCode = 0, $exception = null)
    {
        if (null !== $exception)
        {
            $this->_previousException = $exception;
        }

        $this->_sqlState      = $sqlState;
        $this->_nativeMessage = $nativeMessage;

        parent::__construct($applicationMessage, $vendorCode);
    }

    /**
     * Retrieves the SQLState for this <code>SpicaDatabaseException</code> object
     *
     * @return string The SQLState value
     */
    public function getSqlState()
    {
        return $this->_sqlState;
    }

    /**
     * Retrieves the native message from database server
     *
     */
    public function getNativeMessage()
    {
        if (null !== $this->_nativeMessage)
        {
            return $this->_nativeMessage;
        }

        $current  = $this;

        for (;;)
        {
            $prev = $current->getPreviousException();

            if ($prev    != null)
            {
                $message  = $prev->getNativeMessage();

                if (null !== $message)
                {
                    return $message;
                }

                $current = $prev;
                continue;
            }

            break;
        }
    }

    /**
     * Get previous wrapped exception
     *
     * @return SpicaDatabaseException
     */
    public function getPreviousException()
    {
        return $this->_previousException;
    }

    /**
     * Gets all the messages caused by chain of exceptions
     *
     * The latest message is on the top
     *
     * @return array
     */
    public function getTraceMessages()
    {
        $current    = $this;
        $messages[] = $this->message.' ['.$current->getFile().':'.$current->getLine().'].';

        while (true)
        {
            $prev = $current->getPreviousException();

            if ($prev != null)
            {
                $messages[] = $prev->getMessage();
                $current    = $prev;
                continue;
            }

            break;
        }

        return $messages;
    }
}

/**
 * The subclass of {@link SpicaDatabaseException} thrown when an error
 * occurs during a batch update operation.  In addition to the
 * information provided by {@link SpicaDatabaseException}, a
 * <code>SpicaDatabaseBatchUpdateException</code> provides the update
 * counts for all commands that were executed successfully during the
 * batch update, that is, all commands that were executed before the error
 * occurred.  The order of elements in an array of update counts
 * corresponds to the order in which commands were added to the batch.
 * <P>
 * After a command in a batch update fails to execute properly
 * and a <code>SpicaDatabaseBatchUpdateException</code> is thrown, the driver
 * may or may not continue to process the remaining commands in
 * the batch.  If the driver continues processing after a failure,
 * the array returned by the method
 * <code>SpicaDatabaseBatchUpdateException::getUpdateCounts</code> will have
 * an element for every command in the batch rather than only
 * elements for the commands that executed successfully before
 * the error.  In the case where the driver continues processing
 * commands, the array element for any command
 * that failed is <code>Statement.EXECUTE_FAILED</code>.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      October 30, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaDatabaseBatchUpdateException extends SpicaDatabaseException
{
    /**
     * The array of integers that describes the outcome of a batch execution
     *
     * @var array
     */
    private $_updateCounts = array();

    /**
     * Constructs a <code>SpicaDatabaseBatchUpdateException</code> object initialized with a given
     * <code>$nativeMessage</code>, <code>SQLState</code>, <code>vendorCode</code> and
     * <code>updateCounts</code>.
     * <p>
     *
     * @param string $applicationMessage reason a description of the error
     * @param string $nativeMessage reason a description of the error ( C driver message)
     * @param int $sqlState SQLState an XOPEN or SQL:2003 code identifying the exception
     * @param int $vendorCode an exception code used by a particular database vendor
     * @param array $updateCounts An array of <code>int</code>, with each element
     * indicating the update count, <code>SpicaStatement::SUCCESS_NO_INFO</code> or
     * <code>SpicaStatement::EXECUTE_FAILED</code> for each SQL command in
     * the batch for Connection adapters that continue processing
     * after a command failure; an update count or
     * <code>SpicaStatement::SUCCESS_NO_INFO</code> for each SQL command in the batch
     * prior to the failure for Connection adapters that stop processing after a command
     * failure
     * @param Exception $exception
     */
    public function __construct($applicationMessage, $nativeMessage, $sqlState = null, $vendorCode = null, $updateCounts = null, $exception = null)
    {
        parent::__construct($applicationMessage, $nativeMessage, $sqlState, $vendorCode, $exception);
        $this->_updateCounts = $updateCounts;
    }

    /**
     * Retrieves the update count for each update statement in the batch
     * update that executed successfully before this exception occurred.
     * A driver that implements batch updates may or may not continue to
     * process the remaining commands in a batch when one of the commands
     * fails to execute properly. If the driver continues processing commands,
     * the array returned by this method will have as many elements as
     * there are commands in the batch; otherwise, it will contain an
     * update count for each command that executed successfully before
     * the <code>SpicaDatabaseBatchUpdateException</code> was thrown.
     *
     * @return array An array of <code>int</code> containing the update counts
     * for the updates that were executed successfully before this error
     * occurred.  Or, if the driver continues to process commands after an
     * error, one of the following for every command in the batch:
     * <OL>
     * <LI>an update count
     *  <LI><code>SpicaStatement.SUCCESS_NO_INFO</code> to indicate that the command
     *     executed successfully but the number of rows affected is unknown
     *  <LI><code>SpicaStatement.EXECUTE_FAILED</code> to indicate that the command
     *     failed to execute successfully
     * </OL>
     */
    public function getUpdateCounts()
    {
        return $this->_updateCounts;
    }
}

/**
 * The subclass of {@link SpicaDatabaseException} thrown when the SQLState class value is '<i>0A</i>'
 * ( the value is 'zero' A).
 * This indicates that the database adapter does not support an optional feature.
 * Optional features can fall into the following categories:
 *<p>
 *<ul>
 *<li>no support for an optional feature
 *<li>no support for an optional mode for a method. The mode for a method is determined based on constants
 * passed as parameter values to a method
 *</ul>
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      November 08, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaSQLFeatureNotSupportedException extends SpicaDatabaseException {}

/**
 * The subclass of {@link SpicaDatabaseException} thrown when the connection to a database server
 * can not be established in an allowed time frame.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      April 07, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaConnectionTimeoutException extends SpicaDatabaseException {}

/**
 * The class that defines the constants that are used to identify database vendor
 * or database server software
 * <p>This class is never instantiated.</p>
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      November 09, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ConnectionFactory.php 1711 2010-02-23 18:04:12Z pcdinh $
 */
class SpicaDatabaseVendor
{
    /**
     * Constant to represent Oracle database server
     */
    const ORACLE = 'oracle';

    /**
     * Constant to represent MySQL database server
     */
    const MYSQL = 'mysql';

    /**
     * Constant to represent SQLite3 database
     */
    const SQLITE3 = 'sqlite3';

    /**
     * Constant to represent PostgreSQL database server
     */
    const POSTGRESQL = 'postgresql';

    /**
     * Constant to represent Microsoft SQL Server database server
     */
    const SQLSERVER = 'sqlserver';

    /**
     * Constant to represent Drizzle database server
     */
    const DRIZZLE = 'drizzle';

    /**
     * Prevents instantiation
     */
    private function __construct() {}
}

/**
 * <P>The class that defines the constants that are used to identify generic
 * SQL types, called Pone Database types.
 * <p>
 * This class is never instantiated.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      October 26, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ConnectionFactory.php 1711 2010-02-23 18:04:12Z pcdinh $
 */
class SpicaDatabaseTypes
{
    /**
     * <P>The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>INTEGER</code>.
     */
    const INTEGER = 'int';

    /**
     * <P>The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>NUMERIC</code>.
     */
    const NUMERIC =  'int';

    /**
     * <P>The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>VARCHAR</code>.
     */
    const VARCHAR = 'string';

    /**
     * <P>The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>LONGVARCHAR</code>.
     */
    const LONGVARCHAR = 'long';

    /**
     * <P>The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>DOUBLE</code>.
     */
    const DOUBLE = 'double';

    /**
     * <P>The constant identifies the generic SQL value <code>NULL</code>.
     */
    const NULL = 'null';

    /**
     * The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>BLOB</code>.
     */
    const BLOB = 'blob';

    /**
     * The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>CLOB</code>.
     */
    const CLOB = 'clob';

    /**
     * The constant sometimes referred to as a type code,
     * that identifies the generic SQL type <code>ROWID</code>
     */
    const ROWID = 'rowid';

    /**
     * Prevent instantiation
     */
    private function __construct() {}
}

/**
 * This class provides a set of utility method to work with SQL commands
 *
 * This class is designed to help processing of multiple queries execution
 * and command type dection (READ:SELECT, SHOW ... or WRITE:INSERT, UPDATE query)
 *
 * SQL statements are traditionally divided into the following logical categories:
 * + Data definition statements. These data definition language (DDL) statements can declare, rename, modify, or destroy objects in the local database.
 * + Data manipulation statements. These data manipulation language (DML) statements can retrieve, insert, delete, or modify data values.
 * + Cursor manipulation statements. These statements can declare, open, and close cursors, which are data structures for operations on multiple rows of data.
 * + Dynamic management statements. These statements support memory management and allow users to specify at runtime the details of DML operations.
 * + Data access statements. These statements specify access privileges and support concurrent access to the database by multiple users.
 * + Data integrity statements. These implement transaction logging and support the referential integrity of the database.
 * + Optimization statements. These can be used to improve the performance of operations on the database.
 * + Routine definition statements. These can declare, define, modify, execute, or destroy user-defined routines that the database stores.
 * + Client/server connection statements. These can open or close a connection between a database and a client application.
 * + Auxiliary statements. These can provide information about the database. (This is also a residual category for statements that are not closely related to the other statement categories.)
 * + Optical subsystem statements. These statements are separately documented in IBM Informix Optical Subsystem Guide.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      February 28, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: ConnectionFactory.php 1711 2010-02-23 18:04:12Z pcdinh $
 */
class SpicaDatabaseCommand
{
    /**
     * DML is abbreviation of Data Manipulation Language.
     * It is used to retrieve, store, modify, delete, insert and update data in database.
     * Examples: DELETE, UPDATE, INSERT, MERGE (UPSERT), CALL, LOCK, EXPLAIN statements
     *
     * Some resources day it does include SELECT {@see http://www.orafaq.com/faq/what_are_the_difference_between_ddl_dml_and_dcl_commands}
     * Some don't {@see http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/glossary.htm#sthref4150}
     */
    const ST_DML = 1;

    /**
     * DDL is abbreviation of Data Definition Language.
     * It is used to create and modify the structure of database objects in database.
     * Examples: CREATE, ALTER, DROP, INDEX, TRUNCATE, RENAME, COMMENT statements
     */
    const ST_DDL = 2;

    /**
     * DCL is abbreviation of Data Control Language.
     * It is used to create roles, permissions, and referential integrity as well
     * it is used to control access to database by securing it.
     * Examples: GRANT, REVOKE, DENY statements
     */
    const ST_DCL = 3;

    /**
     * TCL is abbreviation of Transactional Control Language.
     * It is used to manage different transactions occurring within a database.
     * Examples: COMMIT, SAVE POINT, ROLLBACK, SET TRANSACTION statements
     */
    const ST_TCL = 4;

    /**
     * The query to read from a database
     *
     * Examples: SELECT, SHOW, DESC
     */
    const SELECT = 5;

    /**
     * List of string quoting symbols used in SQL
     *
     * @var array
     */
    public static $stringQuotes;

    /**
     * List of field name quoting symbols used in SQL
     *
     * @var array
     */
    public static $identifierQuotes;

    /**
     * List of SQL commenting symbols used in SQL
     *
     * @var array
     */
    public static $sqlComments;

    /**
     * Returns the offset of the query after marked blocks are skipped
     *
     * @param  string $sql
     * @param  int    $position
     * @param  array  $skippedBlocks
     * @return int    The next position to process after any comments or quotes are skipped
     */
    protected static function _skipBlocks($sql, $position = 0, $skippedBlocks)
    {
        for ($i = 0, $blockCount = count($skippedBlocks); $i < $blockCount; $i++)
        {
            $currentBlock   = $skippedBlocks[$i];
            $startMarkerLen = strlen($currentBlock['start']);

            // The starting marker can not be found
            if ($currentBlock['start'] !== substr($sql, $position, $startMarkerLen))
            {
                // Move to another block
                continue;
            }

            // Try to move to the end of the block
            // Assuming that the block is not closed
            $endIndex = false;

            if (true === is_array($currentBlock['end']))
            {
                foreach ($currentBlock['end'] as $symbol)
                {
                    $endIndex  = stripos($sql, $symbol, $position + $startMarkerLen);

                    if (false !== $endIndex)
                    {
                        $currentBlock['end'] = $symbol;
                        break;
                    }
                }
            }
            else
            {
                $endIndex = stripos($sql, $currentBlock['end'], $position + $startMarkerLen);
            }

            if (false !== $endIndex)
            {
                $position = $endIndex + strlen($currentBlock['end']) - 1;
            }
            else
            {
                // character sequence ending comment or quote not found
                $position = strlen($sql); // Move the end of the SQL
            }
        }

        return $position;
    }

    /**
     * Checks if a SQL statement is a SELECT one
     *
     * @todo   It is not a reliable algorithm. Looking for a new way to do it
     * @param  string $sql
     * @return bool
     */
    public static function validateSelect($sql)
    {
        switch (strtolower(substr($sql, 0, 3)))
        {
            case 'sel': // SELECT
            case 'sho': // SHOW
            case 'exp': // EXPLAIN
            case 'des': // DESCRIBE (not DELETE)
                return true;
                break;

            default:
                return false;
        }
    }
}

/**
 * The class allows to create objects that contains part of SQL queries.
 *
 * @see        SpicaStatement#insert()
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      January 15, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaQueryExpression
{
    /**
     * Query expression.
     */
    private $_exp;

    /**
     * Constructs an object of <code>SpicaQueryExpression</code>.
     *
     * @param string $exp
     */
    public function  __construct($exp)
    {
        $this->_exp = $exp;
    }

    /**
     * Gets SQL expression.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->_exp;
    }
}

/**
 * Describes the result of an attempted update.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      January 18, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaUpdateResult
{
    /**
     * The constant indicating that update operations does not meet one or more required conditions.
     *
     * @see SpicaUpdateResult#status
     */
    const COND_FAILED = -1;

    /**
     * The constant indicating that the target of the update does not exist.
     *
     * @see SpicaUpdateResult#status
     */
    const NA = 0;

    /**
     * The constant indicating that an update executed successfully.
     *
     * @see SpicaUpdateResult#status
     */
    const SUCCESS = 1;

    /**
     * Update type: delete
     */
    const DELETE = 5;

    /**
     * Update type: update
     */
    const UPDATE = 6;

    /**
     * Update operation status.
     *
     * @var int
     */
    public $status;

    /**
     * Affected rows after the update
     *
     * @var int
     */
    public $count;

    /**
     * Update type: DELETE or UPDATE
     *
     * @var int
     */
    public $type;

    /**
     * Update message.
     *
     * @var string
     */
    public $message;

    /**
     * Constructs an object of <code>SpicaUpdateResult</code>.
     *
     * @param int $status
     * @param int $type
     * @param int $count Default to 0
     * @param string $message Default to null
     */
    public function  __construct($status, $type, $count = 0, $message = null)
    {
        $this->status = $status;
        $this->count = $count;
        $this->type = $type;
        $this->message = $message;
    }
}

/**
 * The class providers set of common methods that both static statement and prepared statement share.
 *
 * @category   spica
 * @package    core
 * @subpackage datasource\db
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 01, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 */
class SpicaCommonStatement
{
    /**
     * Database connection
     *
     * @var Connection
     */
    protected $_dbConn;

    /**
     * The last query is executed
     *
     * @var string
     */
    protected $_lastQuery;

    /**
     * Flag to indicate that a query is a kind of SELECT or not
     *
     * @var bool
     */
    protected $_isSelect;

    /**
     * Has this statement been closed?
     *
     * @var bool
     */
    protected $_isClosed = false;

    /**
     * Guesses SQL statement type
     *
     * @param string $sql SQL command defaults to null
     * @see   SpicaCommonStatement::_isSelect
     */
    protected function _guessQueryType($sql = null)
    {
        if (null === $sql)
        {
            $sql = $this->_lastQuery;
        }

        $this->_isSelect = SpicaDatabaseCommand::validateSelect($sql);
    }

    /**
     * Checks if the statement object can execute a query that leads to data modification
     *
     * @throws SpicaDatabaseException when database connection is closed or read-only
     */
    protected function _checkUpdatable()
    {
        if (true === $this->_dbConn->isReadOnly())
        {
            throw new SpicaDatabaseException('Connection is read-only. Queries leading to data modification are not allowed. ');
        }

        if (true === $this->_isClosed)
        {
            throw new SpicaDatabaseException('Unable to execute the query because the statement is closed. ');
        }
    }

    /**
     * Checks if the statement object is closed
     *
     * @throws SpicaDatabaseException when the statement is closed
     */
    protected function _checkClosed()
    {
        if (true === $this->_isClosed)
        {
            throw new SpicaDatabaseException('Unable to execute the query because the query statement is closed. ');
        }
    }

    /**
     * Gets the last query
     *
     * @return string
     */
    public function getLastQuery()
    {
        return $this->_lastQuery;
    }

    /**
     * Gets application level connection object in use
     *
     * @return SpicaConnection
     */
    public function getConnection()
    {
        return $this->_dbConn;
    }

    /**
     * Gets the low level connection object
     *
     * @return resource The native database connection
     */
    public function getNativeConnection()
    {
        return $this->_dbConn->getNativeConnection();
    }

    /**
     * Checks if the statement is closed
     *
     * @return bool
     */
    public function isClosed()
    {
        return $this->_isClosed;
    }

    /**
     * Closes the cursor and the statement
     *
     * @return bool
     */
    public function close()
    {
        return $this->_realClose(true);
    }
}

?>